package UMC.Web;

import UMC.Data.JSON;
import UMC.Data.Provider;
import UMC.Data.ProviderConfiguration;
import UMC.Data.Utility;
import UMC.Security.AccessToken;
import UMC.Security.Identity;
import UMC.Web.Activities.SystemSetupActivity;
import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.*;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class WebHost implements HttpHandler {
    public WebHost(String s) {
        this.authSecret = s;
    }

    public static void main(String[] args) throws IOException {
        try {
            URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
            URL f = classLoader.getResource("");
            Manifest manifest;
            if (f != null) {
                File file = new File(f.getPath() + "//META-INF//MANIFEST.MF");
                if (file.exists()) {
                    InputStream inputStream = new FileInputStream(file);
                    manifest = new Manifest(inputStream);
                    inputStream.close();

                    Attributes attributes = manifest.getMainAttributes();
                    String preClass = (String) attributes.get(new Attributes.Name("Premain-Class"));
                    if (!Utility.isEmpty(preClass)) {
                        Class clazz = Class.forName(preClass);
                        Method method = clazz.getMethod("premain", String.class, Instrumentation.class);
                        method.invoke(null, "UMC", null);
                    }
                }
                PackageParse.scanRoot("");


            } else {
                URL url = classLoader.getURLs()[0];
                String jarPath = url.getPath();

                JarFile jarFile = new JarFile(jarPath);

                manifest = jarFile.getManifest();
                jarFile.close();

                Attributes attributes = manifest.getMainAttributes();
                String preClass = (String) attributes.get(new Attributes.Name("Premain-Class"));
                if (!Utility.isEmpty(preClass)) {
                    Class clazz = Class.forName(preClass);
                    Method method = clazz.getMethod("premain", String.class, Instrumentation.class);

                    method.invoke(null, "UMC", null);

                }

            }
        } catch (IOException | IllegalAccessException | NoSuchMethodException | InvocationTargetException |
                 ClassNotFoundException e) {
            e.printStackTrace();
        }
        String authSecret = "";
        ProviderConfiguration providerConfiguration = ProviderConfiguration.configuration("assembly");
        if (providerConfiguration != null) {
            Provider provider = providerConfiguration.get("WebResource");
            if (provider != null) {
                authSecret = provider.get("authSecret");
            }
        }
        int port = 5188;
        Pattern pattern = Pattern.compile("^\\d+$");
        for (String n : args) {
            if (pattern.matcher(n).matches()) {
                port = Utility.parse(n, port);
            } else {
                try {
                    Class.forName(n);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
        UMC.Web.WebServlet.scanning();
        UMC.Web.WebRuntime.register(SystemSetupActivity.class);
        if (Initializer.initializers().length == 0)
            UMC.Web.WebRuntime.register(Initializer.class);
        HttpServer httpServer = HttpServer.create(new InetSocketAddress(port), 512);
        System.out.println(String.format("http://*:%d", port));
        httpServer.createContext("/", new WebHost(authSecret));
        httpServer.setExecutor(Executors.newCachedThreadPool());
        httpServer.start();
    }

    void File(HttpExchange context, String name) throws IOException {
        if (new File(name).exists()) {
            TransmitFile(context, name, true);
        } else {
            context.sendResponseHeaders(404, -1);
        }
    }

    void TransmitFile(HttpExchange context, String file, boolean isCache) throws IOException {
        TransmitFile(context, file, 200, isCache);
    }

    void TransmitFile(HttpExchange context, String file, int statusCode, boolean isCache) throws IOException {
        int lastIndex = file.lastIndexOf('.');
        Headers headers = context.getResponseHeaders();
        if (lastIndex > -1) {
            switch (file.substring(lastIndex + 1).toLowerCase()) {
                case "pdf":
                    headers.set("Content-Type", "application/pdf");
                    break;
                case "log":
                    headers.set("Content-Type", "text/plain; charset=UTF-8");
                    break;
                case "txt":
                    headers.set("Content-Type", "text/plain");
                    break;
                case "htm":
                case "html":
                    headers.set("Content-Type", "text/html");
                    break;
                case "json":
                    headers.set("Content-Type", "text/json");
                    break;
                case "js":
                    headers.set("Content-Type", "text/javascript");
                    break;
                case "css":
                    headers.set("Content-Type", "text/css");
                    break;
                case "bmp":
                    headers.set("Content-Type", "image/bmp");
                    break;
                case "gif":
                    headers.set("Content-Type", "image/gif");
                    break;
                case "webp":
                    headers.set("Content-Type", "image/webp");
                    break;
                case "jpeg":
                case "jpg":
                    headers.set("Content-Type", "image/jpeg");
                    break;
                case "png":
                    headers.set("Content-Type", "image/png");
                    break;
                case "ico":
                    headers.set("Content-Type", "image/x-icon");
                    break;
                case "svg":
                    headers.set("Content-Type", "image/svg+xml");
                    break;
                case "mp3":
                    headers.set("Content-Type", "audio/mpeg");
                    break;
                case "mp4":
                    headers.set("Content-Type", "video/mpeg4");
                    break;
                case "xml":
                    headers.set("Content-Type", "text/xml");
                    break;
                default:
                    headers.set("Content-Type", "application/octet-stream");
                    break;
            }
        } else {
            headers.set("Content-Type", "application/octet-stream");
        }
        File fileInfo = new File(file);

        Date date = new Date();
        date.setTime(fileInfo.lastModified());
        String Modified = date.toString();
        headers.set("Last-Modified", Modified);
        if (isCache) {
            List<String> since = context.getRequestHeaders().get("If-Modified-Since");
            if (since != null && since.size() > 0) {

                if (Modified.equals(since.get(0))) {
                    context.sendResponseHeaders(304, -1);// = 304;
                    return;

                }
            }
        }

        context.sendResponseHeaders(statusCode, fileInfo.length());// = 304;

        OutputStream os = context.getResponseBody();
        FileInputStream is = null;
        try {
            is = new FileInputStream(fileInfo);
            byte[] buffer = new byte[1024];
            int length;
            while ((length = is.read(buffer)) > 0) {
                os.write(buffer, 0, length);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } finally {
            is.close();
            os.close();
        }

    }

    void Resource(HttpExchange context, String name) throws IOException {
        InputStream is = WebHost.class.getResourceAsStream(String.format("/resources/%s.html", name));
        context.getResponseHeaders().set("Content-Type", "text/html");
        context.sendResponseHeaders(200, 0);
        OutputStream os = context.getResponseBody();

        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
        is.close();
        os.close();
    }

    void Temp(HttpExchange context) throws IOException {
        String file = context.getRequestURI().getPath()
                .substring(context.getRequestURI().getPath().indexOf('/', 2) + 1);
        file = Utility.mapPath(String.format("Static/TEMP/%s", file));
        if (file.contains("../")) {

            context.sendResponseHeaders(404, -1);
            return;
        }
        switch (context.getRequestMethod()) {
            case "GET":
                File(context, file);
                break;
            case "PUT":
                InputStream is = context.getRequestBody();
                FileOutputStream os = null;
                File file1 = new File(file);
                file1.getParentFile().mkdirs();
                try {
                    os = new FileOutputStream(file1);
                    byte[] buffer = new byte[1024];
                    int length;
                    while ((length = is.read(buffer)) > 0) {
                        os.write(buffer, 0, length);
                    }
                } catch (FileNotFoundException e) {
                    throw new RuntimeException(e);
                } finally {

                    is.close();
                    os.close();
                }
                context.sendResponseHeaders(200, -1);
                break;
        }
    }

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        try {
            handle2(exchange);
        } catch (Exception e) {
            e.printStackTrace();
            exchange.getResponseHeaders().set("Content-Type", "text/plain");
            exchange.sendResponseHeaders(500, 0);
            PrintStream printStream = new PrintStream(exchange.getResponseBody());
            e.printStackTrace(printStream);
            printStream.close();
            printStream.flush();
            exchange.close();
        }
    }

    void handle2(HttpExchange exchange) throws IOException {

        String[] Segments = Arrays.stream(exchange.getRequestURI().getPath().split("/"))
                .filter(r -> !Utility.isEmpty(r)).collect(Collectors.toList()).toArray(new String[0]);
        if (Segments.length > 0) {
            switch (Segments[0]) {
                case "TEMP":
                    Temp(exchange);
                    return;
                case "UMC":
                    String query = exchange.getRequestURI().getQuery();// .Query;
                    if (!Utility.isEmpty(query) && (query.contains("?_model=") || query.contains("&_model="))) {
                        Process(exchange, Segments);
                    } else if (Segments.length == 2) {
                        switch (Segments[1]) {
                            case "Conf":
                                Conf(exchange);
                                return;
                            default:
                                switch (exchange.getRequestMethod()) {
                                    case "GET":
                                        Resource(exchange, "umc");
                                        break;
                                    default:
                                        Process(exchange, Segments);
                                        break;
                                }
                                return;
                        }
                    } else {
                        switch (exchange.getRequestMethod()) {
                            case "GET":
                                switch (Segments.length) {
                                    case 2:
                                    case 1:
                                        Resource(exchange, "umc");
                                        break;
                                    case 3:
                                        if (exchange.getRequestURI().getPath().endsWith("/")) {
                                            Resource(exchange, "page");
                                        } else {
                                            Resource(exchange, "umc");
                                        }
                                        break;
                                    default:
                                        if (exchange.getRequestURI().getPath().endsWith("/")) {
                                            Resource(exchange, "page");
                                        } else {

                                            Process(exchange, Segments);
                                        }
                                        break;
                                }
                                break;
                            case "OPTIONS":

                                exchange.getResponseHeaders().set("Access-Control-Allow-Origin", "*");
                                exchange.getResponseHeaders().set("Access-Control-Allow-Credentials", "true");
                                exchange.getResponseHeaders().set("Cache-Control", "no-cache");
                                exchange.getResponseHeaders().set("Access-Control-Allow-Headers", "Referer-Path");
                                exchange.sendResponseHeaders(200, -1);
                                break;
                            default:
                                Process(exchange, Segments);
                                break;
                        }
                    }
                    return;
                case "UMC.UI":
                    LocalResources(exchange, "/" + exchange.getRequestURI().getPath().substring(5), true);
                    return;
                case "css":
                case "js":
                    LocalResources(exchange, exchange.getRequestURI().getPath(), true);
                    return;
                default:
                    break;

            }
        }
        String dir = Utility.mapPath("Static");
        String path = exchange.getRequestURI().getPath();

        String file = Utility.pathFormat(dir + path);
        if (file.contains("../")) {

            exchange.sendResponseHeaders(404, -1);
            return;
        }
        File file1 = new File(file);
        if (file1.isFile() && file1.exists()) {
            TransmitFile(exchange, file, true);
            return;
        }
        if (path.indexOf('.', path.lastIndexOf('/')) == -1) {
            String staticFile = Utility.pathFormat(file + "/index.html");

            if (new File(staticFile).exists()) {
                TransmitFile(exchange, staticFile, true);
                return;

            }
        }
        int lastIndex = file.lastIndexOf('.');
        String extName = "html";
        if (lastIndex > -1) {
            extName = file.substring(lastIndex + 1);
        }

        String fok404 = Utility.pathFormat(String.format("%s/404.OK.%s", dir, extName));
        if (new File(fok404).exists()) {
            TransmitFile(exchange, fok404, false);
            return;
        }
        String f404 = Utility.pathFormat(String.format("%s/404.%s", dir, extName));

        if (new File(f404).exists()) {
            TransmitFile(exchange, f404, 404, false);
            return;
        } else if (!"html".equals(extName)) {
            f404 = Utility.pathFormat(String.format("%s/404.html", dir));

            if (new File(f404).exists()) {
                TransmitFile(exchange, f404, 404, false);
                return;
            }
        }

        if (Segments.length == 0) {
            exchange.getResponseHeaders().set("Location", "/UMC");
            exchange.sendResponseHeaders(302, -1);
        } else {
            exchange.sendResponseHeaders(404, -1);
        }

    }

    void LocalResources(HttpExchange context, String path, boolean check) throws IOException {
        String file = Utility.mapPath(String.format("Static%s", path));
        if (file.contains(("../"))) {

            context.sendResponseHeaders(404, -1);
            return;
        }
        File fileInfo = new File(file);
        if (check) {
            if (fileInfo.isFile() && fileInfo.exists()) {
                TransmitFile(context, file, true);
                return;
            }
        }

        URI url = URI.create(String.format("https://res.apiumc.com%s?v%d", path, new Date().getTime()));
        HttpURLConnection connection = (HttpURLConnection) url.toURL().openConnection();
        connection.connect();
        if (connection.getResponseCode() == 200) {
            InputStream is = connection.getInputStream();
            FileOutputStream os = null;
            try {
                fileInfo.getParentFile().mkdirs();
                os = new FileOutputStream(fileInfo);
                byte[] buffer = new byte[1024];
                int length;
                while ((length = is.read(buffer)) > 0) {
                    os.write(buffer, 0, length);
                }
                is.close();
                os.close();

                TransmitFile(context, file, true);
                return;
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {

                is.close();
                os.close();
            }
        }
        context.sendResponseHeaders(404, -1);
    }

    static WebMeta _AppConf = new WebMeta();
    String authSecret;

    void Conf(HttpExchange exchange) throws IOException {
        switch (exchange.getRequestMethod()) {
            case "GET":

                exchange.getResponseHeaders().add("Cache-Control", "no-store");
                exchange.getResponseHeaders().add("Content-Type", "text/javascript;charset=utf-8");

                String cookie = exchange.getRequestHeaders().getFirst("Cookie");
                String seesionKey = "";
                if (!Utility.isEmpty(cookie)) {
                    seesionKey = Arrays.stream(cookie.split("; ")).filter(d -> d.startsWith("device=")).findFirst()
                            .orElse(seesionKey);
                    if (!Utility.isEmpty(seesionKey)) {
                        seesionKey = seesionKey.substring(7);
                    }
                }
                if (Utility.isEmpty(seesionKey)) {

                    seesionKey = Utility.uuid(UUID.randomUUID());
                    exchange.getResponseHeaders().set("Set-Cookie",
                            String.format("device=%s; HttpOnly; Path=/", seesionKey));
                }
                exchange.sendResponseHeaders(200, 0);
                BufferedWriter writer = new BufferedWriter(
                        new OutputStreamWriter(exchange.getResponseBody(), StandardCharsets.UTF_8));
                writer.write("UMC.UI.Config({possrc:'/',posurl: '");
                writer.write("/UMC/");
                writer.write(seesionKey);

                writer.write("'");
                writer.write("});");
                writer.flush();
                writer.close();
                // exchange.se
                return;
        }
        String json = Utility.reader(exchange.getRequestBody());

        WebMeta args = JSON.deserialize(json, WebMeta.class);

        exchange.sendResponseHeaders(200, 0);
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(exchange.getResponseBody(), StandardCharsets.UTF_8));
        String secret = args.get("authSecret");
        if (Utility.isEmpty(this.authSecret)) {

            if (Utility.isEmpty(secret)) {
                JSON.serialize(new WebMeta().put("code", "unactivated", "msg",
                        "未激活").put("appConf", _AppConf), writer);
            } else {

                authSecret = secret;

                ProviderConfiguration providerConfiguration = ProviderConfiguration.configuration("assembly");
                if (providerConfiguration == null) {
                    providerConfiguration = new ProviderConfiguration();
                }
                Provider provider = providerConfiguration.get("WebResource");
                if (provider == null) {
                    provider = Provider.create("WebResource", "WebResource");
                    authSecret = provider.get("authSecret");
                    providerConfiguration.put(provider);
                }
                provider.attributes().put("authSecret", authSecret);

                String domain = args.get("domain");
                if (!Utility.isEmpty(domain)) {
                    provider.attributes().put("domain", domain);
                }
                ProviderConfiguration.configuration("assembly", providerConfiguration);

            }
        } else {
            String sign = args.get("sign");

            StringBuilder buff = new StringBuilder();

            buff.append("appId=");
            buff.append(args.get("appId"));
            buff.append("&time=");
            buff.append(args.get("time"));
            buff.append("&key=");
            buff.append(authSecret);

            if (Utility.md5(buff.toString()).equalsIgnoreCase(sign)) {

                UMC.Data.JSON.serialize(new WebMeta().put("code", "success").put("appConf",
                        _AppConf), writer);

            } else {

                UMC.Data.JSON.serialize(new WebMeta().put("code", "error").put("appConf",
                        _AppConf), writer);
            }
        }

        writer.flush();
        writer.close();
    }

    void Process(HttpExchange exchange, String Segments[]) throws IOException {

        String checkSign = "";

        String seesionKey = "";
        String itme = "";
        int requesttime = 0;
        URI getRequestURI = exchange.getRequestURI();
        String organizes = null, roles = null, id = null, name = null, alias = null, UrlReferrer = null,
                UserAgent = null, UserHostAddress, host = null, scheme = "http", contentType = null;
        UserHostAddress = exchange.getRemoteAddress().getAddress().getHostAddress();
        Set<Map.Entry<String, List<String>>> headers = exchange.getRequestHeaders().entrySet();
        Map<String, String> authing = new LinkedHashMap<>();

        for (Map.Entry<String, List<String>> k : headers) {
            String k2 = k.getKey().toLowerCase();
            String value2 = k.getValue().get(0);
            String value = URLDecoder.decode(value2);//String.join(",", k.getValue()));
            if (k2.startsWith("umc-")) {
                if (k2.endsWith("-sign")) {
                    checkSign = value2;
                } else {
                    authing.put(k2, value2);
                }
            }
            switch (k2) {
                case "umc-request-time":
                    requesttime = Utility.parse(value, 0);// value;
                    break;
                case "itme-access-token":
                    itme = value;
                case "umc-request-user-organizes":
                    organizes = value;
                    break;
                case "umc-request-user-roles":
                    roles = value;
                    break;
                case "umc-request-user-id":
                    id = value;
                    break;
                case "umc-request-user-name":
                    name = value;
                    break;
                case "umc-request-user-alias":
                    alias = value;
                    break;
                case "referer":
                    UrlReferrer = value;
                    break;
                case "user-agent":
                    UserAgent = value;
                    break;
                case "cookie":
                    seesionKey = Arrays.stream(value.split("; ")).filter(d -> d.startsWith("device=")).findFirst()
                            .orElse("");
                    if (!Utility.isEmpty(seesionKey)) {
                        seesionKey = seesionKey.substring(7);
                    }
                    break;
                case "content-type":
                    contentType = value;
                    break;
                case "x-forwarded-host":
                case "x-client-host":
                    host = value;
                    break;
                case "host":
                    if (Utility.isEmpty(host))
                        host = value;
                    break;
                case "x-real-ip":
                case "x-forwarded-for":
                case "x-client-ip":
                    UserHostAddress = value;
                    break;
                case "x-forwarded-proto":
                case "x-client-scheme":
                    scheme = value;
                    break;
            }
        }

        Map<String, String> query = Utility.queryString(getRequestURI.getQuery());
        if (!Utility.isEmpty(contentType) && contentType.contains("www-form-urlencoded")) {
            Utility.queryString(Utility.reader(exchange.getRequestBody()), query);
        }
        Map<String, String> QueryString = new LinkedHashMap<>();

        String model = null, cmd = null;

        Set<Map.Entry<String, String>> set = query.entrySet();
        List<String> values = new LinkedList<>();
        for (Map.Entry<String, String> entry : set) {
            String key = entry.getKey();
            switch (key) {
                case "_model":
                    model = String.join(",", entry.getValue());
                    break;
                case "_cmd":
                    cmd = String.join(",", entry.getValue());
                    break;
                case "_":
                    if (!Utility.isEmpty(entry.getValue()))
                        values.add(entry.getValue());
                    break;
                case "__":
                    for (String v2 : entry.getValue().split("/")) {
                        if (!Utility.isEmpty(v2))
                            values.add(v2);
                    }
                    break;
                default:
                    String v = entry.getValue();
                    if (key.startsWith("_")) {
                        if (Utility.isEmpty(v)) {
                            values.add(key);
                        }
                    } else {
                        QueryString.put(key, v);
                    }
                    break;
            }
        }

        if (QueryString.size() == 1) {
            Map.Entry<String, String> entry = QueryString.entrySet().stream().findFirst().get();
            if (Utility.isEmpty(entry.getKey())) {
                if (!Utility.isEmpty(entry.getValue())) {
                    values.add(entry.getValue());
                }
                QueryString.clear();

            } else if (Utility.isEmpty(entry.getValue())) {
                values.add(entry.getKey());
                QueryString.clear();
            }

        }
        boolean IsForm = false;
        switch (Segments.length) {
            case 1:
            case 0:
                if (Utility.isEmpty(seesionKey)) {
                    seesionKey = Utility.uuid(UUID.randomUUID());
                    exchange.getResponseHeaders().set("Set-Cookie",
                            String.format("device=%s; HttpOnly; Path=/", seesionKey));
                }
                break;
            case 2:
                seesionKey = Segments[1];
                IsForm = getRequestURI.getPath().endsWith("/");
                break;
            default:
                model = Segments[1];
                cmd = Segments[2];
                for (int i = 3; i < Segments.length; i++) {
                    values.add(Segments[i]);
                }
                break;
        }

        if (Utility.isEmpty(cmd) || Utility.isEmpty(model)) {
            exchange.getResponseHeaders().set("Content-Type", "text/plain");
            byte[] bytes = "command is empty".getBytes();
            exchange.sendResponseHeaders(404, bytes.length);
            exchange.getResponseBody().write(bytes, 0, bytes.length);
            return;
        }

        AccessToken accessToken = null;
        if (!Utility.isEmpty(authSecret) && !Utility.isEmpty(checkSign) && !Utility.isEmpty(name)) {
            StringBuilder buff = new StringBuilder();
            List<String> stream = authing.keySet().stream().sorted().collect(Collectors.toList());
            for (String k : stream) {
                buff.append(k);
                buff.append("=");
                buff.append(authing.get(k));
                buff.append("&");
            }
            buff.append("key=");
            buff.append(authSecret);
            if (Utility.md5(buff.toString()).equalsIgnoreCase(checkSign)) {
                UUID userId = Utility.uuid(name, true);
                if (!Utility.isEmpty(id)) {
                    userId = Utility.isNull(Utility.uuid(id), userId);
                }
                Identity user = Identity.create(userId, name, alias,
                        Utility.isEmpty(roles) ? new String[0] : roles.split(","),
                        Utility.isEmpty(organizes) ? new String[0] : organizes.split(","));


                ItmeToken token = new ItmeToken(exchange, Utility.uuid(seesionKey, true));

                token.login(user, 0);
                token.init(itme);
                accessToken = token;

            }
        }
        if (accessToken == null) {
            requesttime = 0;
            UUID uuid = Utility.uuid(seesionKey, true);
            accessToken = new AccessToken(uuid).login(Identity.create(uuid, "?", "Guest"), 0);
        }
        if (Utility.isEmpty(host)) {
            host = getRequestURI.getAuthority();
        }
        URI _uri = URI.create(String.format("%s://%s%s", scheme, host, getRequestURI));
        WebClient client = new WebClient(_uri, Utility.isEmpty(UrlReferrer) ? null : URI.create(UrlReferrer), UserAgent,
                UserHostAddress, accessToken, requesttime);
        client.isForm = IsForm;
        if (client.verify(model, cmd)) {
            WebMeta header = new WebMeta();
            if (QueryString.size() > 0) {
                header.put(model, QueryString);
            }
            switch (values.size()) {
                case 0:
                    break;
                case 1:
                    header.put(cmd, values.get(0));
                    break;
                default:
                    header.put(cmd, values.toArray());
                    break;
            }
            client.send(model, cmd, header);

        }
        BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(exchange.getResponseBody(), StandardCharsets.UTF_8));

        client.writeTo(new WebClient.IWebClient() {
            @Override
            public Writer outputWriter() throws IOException {
                exchange.getResponseHeaders().set("Content-Type", "application/json");
                exchange.sendResponseHeaders(200, 0);
                return writer;
            }

            @Override
            public void redirect(String uri) throws IOException {
                exchange.getResponseHeaders().set("Location", uri);
                exchange.sendResponseHeaders(302, -1);
            }
        });
        writer.flush();
        writer.close();
    }

    private class ItmeToken extends AccessToken {
        public ItmeToken(HttpExchange c, UUID guid) {
            super(guid);
            this.context = c;
        }

        HttpExchange context;

        @Override
        public void commit() {
            super.commit();
            context.getResponseHeaders().add("itme-access-token", Base64.getEncoder().encodeToString(UMC.Data.JSON.serialize(this.Data).getBytes()));

        }

        public void init(String itme) {
            if (Utility.isEmpty(itme) == false) {
                WebMeta item = JSON.deserialize(new String(Base64.getDecoder().decode(itme)), WebMeta.class);
                item.map().forEach((r, v) -> this.Data.put(r, (String) v));


            }
        }

    }
}
